#include <map>
#include <nice2d/application.h>
#include <nice2d/objectfactory.h>
#include <nice2d/widgets/dock.h>
#include <nice2d/widgets/desktop.h>
#include <nice2d/widgets/layout.h>
#include <nice2d/widgets/label.h>
#include <nice2d/widgets/basicbutton.h>
#include <nice2d/widgets/button.h>
#include <nice2d/widgets/progressbar.h>
#include <nice2d/widgets/draggablelabel.h>
#include <nice2d/widgets/titlebar.h>
#include <nice2d/widgets/panel.h>
#include <nice2d/widgets/menu.h>
#include <nice2d/widgets/messagelistcontrol.h>
#include <nice2d/widgets/linecontrol.h>
#include <nice2d/widgets/slider.h>
#include <nice2d/widgets/checkbox.h>
#include <nice2d/widgets/texttable.h>
#include <nice2d/widgets/radiobox.h>
#include <nice2d/widgets/directionbutton.h>

struct TopLevelControl
{
    ControlPointer control;
    int zVal;
    std::shared_ptr<Dock> dock;

    TopLevelControl(ControlPointer control,int z,std::shared_ptr<Dock> d = nullptr):
        control(control),
        zVal(z)
    {
        if(d)
            dock = d;
    }

    TopLevelControl(const TopLevelControl& tlw)
    {
        (*this) = tlw;
    }

    TopLevelControl& operator = (const TopLevelControl& tlw)
    {
        control = tlw.control;
        zVal = tlw.zVal;
        dock = tlw.dock;
        return *this;
    }

    bool operator == (const TopLevelControl& tlw) const
    {
        return (control == tlw.control);
    }

    bool operator != (const TopLevelControl& tlw) const
    {
        return !((*this) == tlw);
    }

    bool operator > (const TopLevelControl& tlw) const
    {
        return (zVal > tlw.zVal);
    }
};

class DesktopData
{
public:
    DesktopData()
    {
#define INSTALL_STYLE(control,controlStyle)\
        {\
            auto style = std::make_shared<controlStyle>();\
            styles.insert_or_assign(control,style);\
        }

        INSTALL_STYLE("Layout",LayoutStyle);
        INSTALL_STYLE("Label",LabelStyle);
        INSTALL_STYLE("BasicButton",BasicButtonStyle);
        INSTALL_STYLE("Button",ButtonStyle);
        INSTALL_STYLE("ProgressBar",ProgressBarStyle);
        //INSTALL_STYLE("DraggableLabel",DraggableLabelStyle);
        INSTALL_STYLE("TitleBar",TitleBarStyle);
        INSTALL_STYLE("Panel",PanelStyle);
        INSTALL_STYLE("Menu",MenuStyle);
        INSTALL_STYLE("MessageListControl",MessageListControlStyle);
        INSTALL_STYLE("LineControl",LineControlStyle);
        INSTALL_STYLE("Slider",SliderStyle);
        INSTALL_STYLE("CheckBox",CheckBoxStyle);
        INSTALL_STYLE("TextTable",TextTableStyle);
        INSTALL_STYLE("RadioBox",RadioBoxStyle);
        INSTALL_STYLE("DirectionButton",DirectionButtonStyle);
    }

    Desktop* desktop = nullptr;
    typedef std::list<TopLevelControl> Controls;
    Controls controls;

    ControlPointer hoverControl;
    ControlPointer focusedControl;
    ControlPointer popupControl;
    ControlPointer belowCursorControl;
    ControlPointer oldBelowCursorControl;
    ControlPointer capturedControl;

    bool ditryZValue = false;

    std::map<std::string,std::shared_ptr<ControlStyle>> styles;

    void updateFocusIfNecessary()
    {
        if((popupControl == 0) || popupControl->isUnderMouse())
        {
            if(belowCursorControl != focusedControl)
                desktop->setFocusWidget(belowCursorControl);
        }
    }
};

Desktop::Desktop():
    data(new DesktopData())
{
    data->desktop = this;
}

Desktop::~Desktop()
{
}

std::shared_ptr<Dock> Desktop::addWidget(ControlPointer control)
{
    if (!control)
        return nullptr;

    TopLevelControl tlc(control,largestZValue() + 1);
    tlc.dock = std::make_shared<Dock>();
    data->controls.push_back(tlc);
    data->controls.sort(std::greater<TopLevelControl>());
    return tlc.dock;
}

void Desktop::removeWidget(ControlPointer control)
{
    if (!control)
        return;

    data->controls.remove_if([&control](const TopLevelControl& tlc) {
        return control == tlc.control;
    });

    if(data->belowCursorControl)
    {
        if(control->doesHierarchyContain(data->belowCursorControl))
            checkForControlBelowCursor();
    }

    if(data->focusedControl)
    {
        if(control->doesHierarchyContain(data->focusedControl))
            data->focusedControl.reset();
    }
}

void Desktop::clearAllWidgets()
{
    data->controls.clear();
    data->belowCursorControl = nullptr;
    data->oldBelowCursorControl = nullptr;
    data->capturedControl = nullptr;
    data->focusedControl = nullptr;
    data->popupControl = nullptr;
}

ControlPointer Desktop::getFocusWidget() const
{
    return data->focusedControl;
}

void Desktop::setFocusWidget(ControlPointer control)
{
    if (!control)
        return;

    if (control != data->focusedControl) {
        if (data->focusedControl)
        {
            data->focusedControl->setFocused(false);
            data->focusedControl->setZValue(data->focusedControl->getZValue()+1);
            data->ditryZValue = true;
        }
        data->focusedControl = control;
        data->focusedControl->setFocused(true);
    }
}

ControlPointer Desktop::getPopupWidget()const
{
    return data->popupControl;
}

void Desktop::setPopupWidget(ControlPointer widget)
{
    if (!widget)
        return;

    if (widget != data->popupControl) {
        data->popupControl = widget;
    }
}

ControlPointer Desktop::widgetBelowCursor() const
{
    return data->belowCursorControl;
}

bool Desktop::isTopWidget(ControlPointer control) const
{
    bool ret = false;
    for(auto it = data->controls.begin(); it != data->controls.end(); ++it)
    {
        if((*it).control == control)
        {
            ret = true;
            break;
        }
    }
    return ret;
}

void Desktop::setMouseCaptureControl(ControlPointer control)
{
    data->capturedControl = control;
}

void Desktop::releaseMouseCaptureControl()
{
    data->capturedControl = nullptr;
}

ControlPointer Desktop::getMouseCaptureControl() const
{
    return data->capturedControl;
}

int Desktop::largestZValue() const
{
    int ret = 0;
    if(!data->controls.empty())
    {
        auto it = data->controls.begin();
        ret = (*it).zVal;
        while(it != data->controls.end())
        {
            auto& tlw = *it;
            if(tlw.zVal > ret)
                ret = tlw.zVal;
            ++it;
        }
    }
    return ret;
}

void Desktop::registerControlStyle(const std::string &control, std::shared_ptr<ControlStyle> style)
{
    if(style)
        data->styles.insert_or_assign(control,style);
}

std::list<std::string> Desktop::getControlStyles() const
{
    std::list<std::string> output;
    auto itr = data->styles.begin();
    while(itr != data->styles.end())
    {
        output.push_back(itr->first);
        itr ++;
    }
    return output;
}

std::shared_ptr<ControlStyle> Desktop::getControlStyle(const std::string &control)
{
    auto itr = data->styles.find(control);
    return itr != data->styles.end() ? itr->second : nullptr;
}

void Desktop::checkForControlBelowCursor()
{
    auto mousePosition = Application::getInstance()->getMousePosition();
    sf::Vector2i position(mousePosition.x,mousePosition.y);

    bool done = false;
    if(data->popupControl)
    {
        auto control = data->popupControl->getControlBelow(position);
        if(data->popupControl->doesHierarchyContain(control))
        {
            data->belowCursorControl = data->popupControl;
            done = true;
        }
    }

    if(!done)
    {
        ControlPointer control = 0;
        auto itr = data->controls.begin();
        while(itr != data->controls.end())
        {
            auto tlc = *itr;
            if(tlc.control->isVisible())
            {
                auto current = tlc.control->getControlBelow(position);
                if(current)
                {
                    control = current;
                    break;
                }
            }
            itr ++;
        }
        data->belowCursorControl = control;
    }
}

bool Desktop::process(std::shared_ptr<Message> message)
{
    if (message->getMessageType() != MessageType_SFML)
        return false;

    auto sfmlMessage = std::dynamic_pointer_cast<SFMLMessage>(message);

    if(data->popupControl)
    {
        if(data->popupControl->process(message))
            return true;
    }

    if(data->focusedControl)
    {
        if(data->focusedControl->process(message))
            return true;
    }

    bool ret = false;

    auto itr = data->controls.rbegin();
    while (itr != data->controls.rend()) {
        if((*itr).control->process(message))
        {
            return true;
        }
        itr ++;
    }
    return ret;
}

void Desktop::update(float deltaTime)
{
    if(data->ditryZValue)
    {
        data->ditryZValue = false;
        //TODO...
    }

    auto itr = data->controls.begin();
    while (itr != data->controls.end()) {
        TopLevelControl tlc = *itr;
        tlc.control->update(deltaTime);
        if(tlc.dock)
        {
            auto size = Application::getInstance()->getWindow()->getSize();
            tlc.dock->adjustControl(tlc.control,{(float)size.x,(float)size.y});
        }

        itr ++;
    }
}

void Desktop::draw(sf::RenderTarget &target, sf::RenderStates states) const
{
    auto itr = data->controls.begin();
    while (itr != data->controls.end()) {
        (*itr).control->draw(target,states);
        itr ++;
    }

    if(data->focusedControl)
        data->focusedControl->draw(target,states);

    if (data->popupControl)
        data->popupControl->draw(target, states);
}
